#pragma once

//-----------------------------------------------------------------------------
// mvTextEditor
//
//     - This was forked and modified from the now abandoned 
//       ImGuiColorTextEditor.
//       
//     - Eventually this should be rewritten and improved to make it
//       more extendable and customizable (i.e. key mappings)
//     
//-----------------------------------------------------------------------------

#include <string>
#include <vector>
#include <array>
#include <memory>
#include <unordered_set>
#include <unordered_map>
#include <map>
#include <regex>
#include "imgui.h"

namespace Marvel {

	//-----------------------------------------------------------------------------
	// mvTextEditor
	//-----------------------------------------------------------------------------
	class mvTextEditor
	{
	public:
		enum class PaletteIndex
		{
			Default,
			Keyword,
			Number,
			String,
			CharLiteral,
			Punctuation,
			Preprocessor,
			Identifier,
			KnownIdentifier,
			PreprocIdentifier,
			Comment,
			MultiLineComment,
			Background,
			Cursor,
			Selection,
			ErrorMarker,
			Breakpoint,
			LineNumber,
			CurrentLineFill,
			CurrentLineFillInactive,
			CurrentLineEdge,
			Max
		};

		enum class SelectionMode
		{
			Normal,
			Word,
			Line
		};

		struct Breakpoint
		{
			int mLine;
			bool mEnabled;
			std::string mCondition;

			Breakpoint()
				: mLine(-1)
				, mEnabled(false)
			{}
		};

		// Represents a character coordinate from the user's point of view,
		// i. e. consider an uniform grid (assuming fixed-width font) on the
		// screen as it is rendered, and each cell has its own coordinate, starting from 0.
		// Tabs are counted as [1..mTabSize] count empty spaces, depending on
		// how many space is necessary to reach the next tab stop.
		// For example, coordinate (1, 5) represents the character 'B' in a line "\tABC", when mTabSize = 4,
		// because it is rendered as "    ABC" on the screen.
		struct Coordinates
		{
			int mLine, mColumn;
			Coordinates() : mLine(0), mColumn(0) {}
			Coordinates(int aLine, int aColumn) : mLine(aLine), mColumn(aColumn)
			{
				assert(aLine >= 0);
				assert(aColumn >= 0);
			}
			static Coordinates Invalid() { static Coordinates invalid(-1, -1); return invalid; }

			bool operator ==(const Coordinates& o) const
			{
				return
					mLine == o.mLine &&
					mColumn == o.mColumn;
			}

			bool operator !=(const Coordinates& o) const
			{
				return
					mLine != o.mLine ||
					mColumn != o.mColumn;
			}

			bool operator <(const Coordinates& o) const
			{
				if (mLine != o.mLine)
					return mLine < o.mLine;
				return mColumn < o.mColumn;
			}

			bool operator >(const Coordinates& o) const
			{
				if (mLine != o.mLine)
					return mLine > o.mLine;
				return mColumn > o.mColumn;
			}

			bool operator <=(const Coordinates& o) const
			{
				if (mLine != o.mLine)
					return mLine < o.mLine;
				return mColumn <= o.mColumn;
			}

			bool operator >=(const Coordinates& o) const
			{
				if (mLine != o.mLine)
					return mLine > o.mLine;
				return mColumn >= o.mColumn;
			}
		};

		struct Identifier
		{
			Coordinates mLocation;
			std::string mDeclaration;
		};

		typedef std::string String;
		typedef std::unordered_map<std::string, Identifier> Identifiers;
		typedef std::unordered_set<std::string> Keywords;
		typedef std::map<int, std::string> ErrorMarkers;
		typedef std::unordered_set<int> Breakpoints;
		typedef std::array<ImU32, (unsigned)PaletteIndex::Max> Palette;
		typedef uint8_t Char;

		struct Glyph
		{
			Char mChar;
			PaletteIndex mColorIndex = PaletteIndex::Default;
			bool mComment : 1;
			bool mMultiLineComment : 1;
			bool mPreprocessor : 1;

			Glyph(Char aChar, PaletteIndex aColorIndex) : mChar(aChar), mColorIndex(aColorIndex),
				mComment(false), mMultiLineComment(false), mPreprocessor(false) {}
		};

		typedef std::vector<Glyph> Line;
		typedef std::vector<Line> Lines;

		struct LanguageDefinition
		{
			typedef std::pair<std::string, PaletteIndex> TokenRegexString;
			typedef std::vector<TokenRegexString> TokenRegexStrings;
			typedef bool(*TokenizeCallback)(const char* in_begin, const char* in_end, const char*& out_begin, const char*& out_end, PaletteIndex& paletteIndex);

			std::string mName;
			Keywords mKeywords;
			Identifiers mIdentifiers;
			Identifiers mPreprocIdentifiers;
			std::string mCommentStart, mCommentEnd, mSingleLineComment;
			char mPreprocChar;
			bool mAutoIndentation;

			TokenizeCallback mTokenize;

			TokenRegexStrings mTokenRegexStrings;

			bool mCaseSensitive;

			LanguageDefinition()
				: mPreprocChar('#'), mAutoIndentation(true), mTokenize(nullptr), mCaseSensitive(true)
			{
			}

			static const LanguageDefinition& CPlusPlus();
			static const LanguageDefinition& HLSL();
			static const LanguageDefinition& GLSL();
			static const LanguageDefinition& C();
			static const LanguageDefinition& SQL();
			static const LanguageDefinition& AngelScript();
			static const LanguageDefinition& Lua();
			static const LanguageDefinition& Python();
		};

		mvTextEditor();
		~mvTextEditor();

		void ClearUndo() { mUndoBuffer.clear(); mUndoIndex = 0; }

		void SetLanguageDefinition(const LanguageDefinition& aLanguageDef);
		const LanguageDefinition& GetLanguageDefinition() const { return mLanguageDefinition; }

		const Palette& GetPalette() const { return mPaletteBase; }
		void SetPalette(const Palette& aValue);

		void SetErrorMarkers(const ErrorMarkers& aMarkers) { mErrorMarkers = aMarkers; }
		void SetBreakpoints(const Breakpoints& aMarkers) { mBreakpoints = aMarkers; }

		void Render(const char* aTitle, const ImVec2& aSize = ImVec2(), bool aBorder = false);
		void SetText(const std::string& aText);
		std::string GetText() const;

		void SetTextLines(const std::vector<std::string>& aLines);
		std::vector<std::string> GetTextLines() const;

		std::string GetSelectedText() const;
		std::string GetCurrentLineText()const;

		int GetTotalLines() const { return (int)mLines.size(); }
		bool IsOverwrite() const { return mOverwrite; }

		void SetReadOnly(bool aValue);
		bool IsReadOnly() const { return mReadOnly; }
		bool IsTextChanged() const { return mTextChanged; }
		bool IsCursorPositionChanged() const { return mCursorPositionChanged; }

		bool IsColorizerEnabled() const { return mColorizerEnabled; }
		void SetColorizerEnable(bool aValue);

		Coordinates GetCursorPosition() const { return GetActualCursorCoordinates(); }
		void SetCursorPosition(const Coordinates& aPosition);

		inline void SetHandleMouseInputs(bool aValue) { mHandleMouseInputs = aValue; }
		inline bool IsHandleMouseInputsEnabled() const { return mHandleKeyboardInputs; }

		inline void SetHandleKeyboardInputs(bool aValue) { mHandleKeyboardInputs = aValue; }
		inline bool IsHandleKeyboardInputsEnabled() const { return mHandleKeyboardInputs; }

		inline void SetImGuiChildIgnored(bool aValue) { mIgnoreImGuiChild = aValue; }
		inline bool IsImGuiChildIgnored() const { return mIgnoreImGuiChild; }

		inline void SetShowWhitespaces(bool aValue) { mShowWhitespaces = aValue; }
		inline bool IsShowingWhitespaces() const { return mShowWhitespaces; }

		void       SetTabSize(int aValue);
		inline int GetTabSize() const { return mTabSize; }

		void InsertText(const std::string& aValue);
		void InsertText(const char* aValue);

		void MoveUp(int aAmount = 1, bool aSelect = false);
		void MoveDown(int aAmount = 1, bool aSelect = false);
		void MoveLeft(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
		void MoveRight(int aAmount = 1, bool aSelect = false, bool aWordMode = false);
		void MoveTop(bool aSelect = false);
		void MoveBottom(bool aSelect = false);
		void MoveHome(bool aSelect = false);
		void MoveEnd(bool aSelect = false);

		void SetSelectionStart(const Coordinates& aPosition);
		void SetSelectionEnd(const Coordinates& aPosition);
		void SetSelection(const Coordinates& aStart, const Coordinates& aEnd, SelectionMode aMode = SelectionMode::Normal);
		void SelectWordUnderCursor();
		void SelectAll();
		bool HasSelection() const;

		void Copy();
		void Cut();
		void Paste();
		void Delete();

		bool CanUndo() const;
		bool CanRedo() const;
		void Undo(int aSteps = 1);
		void Redo(int aSteps = 1);

		static const Palette& GetDarkPalette();
		static const Palette& GetLightPalette();
		static const Palette& GetRetroBluePalette();

	private:

		typedef std::vector<std::pair<std::regex, PaletteIndex>> RegexList;

		struct EditorState
		{
			Coordinates mSelectionStart;
			Coordinates mSelectionEnd;
			Coordinates mCursorPosition;
		};

		class UndoRecord
		{
		public:
			UndoRecord() {}
			~UndoRecord() {}

			UndoRecord(
				const std::string& aAdded,
				const mvTextEditor::Coordinates aAddedStart,
				const mvTextEditor::Coordinates aAddedEnd,

				const std::string& aRemoved,
				const mvTextEditor::Coordinates aRemovedStart,
				const mvTextEditor::Coordinates aRemovedEnd,

				mvTextEditor::EditorState& aBefore,
				mvTextEditor::EditorState& aAfter);

			void Undo(mvTextEditor* aEditor);
			void Redo(mvTextEditor* aEditor);

			std::string mAdded;
			Coordinates mAddedStart;
			Coordinates mAddedEnd;

			std::string mRemoved;
			Coordinates mRemovedStart;
			Coordinates mRemovedEnd;

			EditorState mBefore;
			EditorState mAfter;
		};

		typedef std::vector<UndoRecord> UndoBuffer;

		void ProcessInputs();
		void Colorize(int aFromLine = 0, int aCount = -1);
		void ColorizeRange(int aFromLine = 0, int aToLine = 0);
		void ColorizeInternal();
		float TextDistanceToLineStart(const Coordinates& aFrom) const;
		void EnsureCursorVisible();
		int GetPageSize() const;
		std::string GetText(const Coordinates& aStart, const Coordinates& aEnd) const;
		Coordinates GetActualCursorCoordinates() const;
		Coordinates SanitizeCoordinates(const Coordinates& aValue) const;
		void Advance(Coordinates& aCoordinates) const;
		void DeleteRange(const Coordinates& aStart, const Coordinates& aEnd);
		int InsertTextAt(Coordinates& aWhere, const char* aValue);
		void AddUndo(UndoRecord& aValue);
		Coordinates ScreenPosToCoordinates(const ImVec2& aPosition) const;
		Coordinates FindWordStart(const Coordinates& aFrom) const;
		Coordinates FindWordEnd(const Coordinates& aFrom) const;
		Coordinates FindNextWord(const Coordinates& aFrom) const;
		int GetCharacterIndex(const Coordinates& aCoordinates) const;
		int GetCharacterColumn(int aLine, int aIndex) const;
		int GetLineCharacterCount(int aLine) const;
		int GetLineMaxColumn(int aLine) const;
		bool IsOnWordBoundary(const Coordinates& aAt) const;
		void RemoveLine(int aStart, int aEnd);
		void RemoveLine(int aIndex);
		Line& InsertLine(int aIndex);
		void EnterCharacter(ImWchar aChar, bool aShift);
		void Backspace();
		void DeleteSelection();
		std::string GetWordUnderCursor() const;
		std::string GetWordAt(const Coordinates& aCoords) const;
		ImU32 GetGlyphColor(const Glyph& aGlyph) const;

		void HandleKeyboardInputs();
		void HandleMouseInputs();
		void Render();

		float mLineSpacing;
		Lines mLines;
		EditorState mState;
		UndoBuffer mUndoBuffer;
		int mUndoIndex;

		int mTabSize;
		bool mOverwrite;
		bool mReadOnly;
		bool mWithinRender;
		bool mScrollToCursor;
		bool mScrollToTop;
		bool mTextChanged;
		bool mColorizerEnabled;
		float mTextStart;                   // position (in pixels) where a code line starts relative to the left of the TextEditor.
		int  mLeftMargin;
		bool mCursorPositionChanged;
		int mColorRangeMin, mColorRangeMax;
		SelectionMode mSelectionMode;
		bool mHandleKeyboardInputs;
		bool mHandleMouseInputs;
		bool mIgnoreImGuiChild;
		bool mShowWhitespaces;

		Palette mPaletteBase;
		Palette mPalette;
		LanguageDefinition mLanguageDefinition;
		RegexList mRegexList;

		bool mCheckComments;
		Breakpoints mBreakpoints;
		ErrorMarkers mErrorMarkers;
		ImVec2 mCharAdvance;
		Coordinates mInteractiveStart, mInteractiveEnd;
		std::string mLineBuffer;
		uint64_t mStartTime;

		float mLastClick;
	};
}